package com.leyou.search.service.impl;


import com.leyou.common.exception.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.pojo.Goods;
import com.leyou.pojo.SearchRequest;
import com.leyou.repository.GoodRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: YHL
 * @time: 2021/2/27 21:20
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodRepository goodRepository;

    @Autowired
    private ItemClient itemClient;


    @Override
    public Mono<List<String>> getSuggest(String key) {
        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "匹配参数不能为空");
        }


        return this.goodRepository.suggestBySingleField("suggestion", key);
    }

    @Override
    public Mono<PageInfo<Goods>> listData(SearchRequest searchRequest) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();


        sourceBuilder.query(buildQuery(searchRequest));

        //分页条件
        sourceBuilder.from((searchRequest.getPage() - 1) * searchRequest.getSIZE());

        //分页处理中每一页的内容数量
        sourceBuilder.size(searchRequest.getSIZE());

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");
        highlightBuilder.field("title");

        //高亮条件
        sourceBuilder.highlighter(highlightBuilder);

        //获取排序条件
        String sortField = searchRequest.getSortBy();

        if (!StringUtils.isEmpty(sortField)) {

            //如果排序字段不为空，并且需要根据desc的true和false处理具体排序的方式
            sourceBuilder.sort(
                    SortBuilders
                            .fieldSort(sortField)
                            .order(searchRequest.getDesc() ? SortOrder.DESC : SortOrder.ASC));
        }


        //查询，分页，高亮，排序
        return this.goodRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);

    }


    //构建查询
    private QueryBuilder buildQuery(SearchRequest searchRequest) {

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();


        String key = searchRequest.getKey();
        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "查询条件不能为空");
        }

        //添加查询条件
        queryBuilder.must(QueryBuilders.matchQuery("title", searchRequest.getKey()).operator(Operator.AND));

        //过滤条件，
        Map<String, String> filters = searchRequest.getFilters();

        //判断是否有过滤条件
        if (!CollectionUtils.isEmpty(filters)) {

            for (Map.Entry<String, String> filterEntry : filters.entrySet()) {

                //本次选中的过滤条件的key
                String filterKey = filterEntry.getKey();
                //过滤条件的value
                String filterValue = filterEntry.getValue();

                //如果过滤条件的key为 品牌，则表示本次以品牌过滤
                if ("品牌".equals(filterKey)) {
                    queryBuilder.filter(QueryBuilders.termQuery("brandId", filterValue));
                } else if ("分类".equals(filterKey)) {
                    queryBuilder.filter(QueryBuilders.termQuery("categoryId", filterValue));
                } else {

                    //其他规格参数的过滤，传递给后台的过滤条件map的key其实本质上是，规格参数specs.name的值，map的value是specs.value的值

                    //构建boolean查询，既需要匹配specs.name,又需要匹配specs.value
                    BoolQueryBuilder internalBoolQuery = QueryBuilders.boolQuery();

                    internalBoolQuery.must(QueryBuilders.termQuery("specs.name",filterKey));
                    internalBoolQuery.must(QueryBuilders.termQuery("specs.value",filterValue));

                    queryBuilder.filter(QueryBuilders.nestedQuery("specs",internalBoolQuery, ScoreMode.None));
                }
            }
        }

        //查询
        return queryBuilder;
    }

    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchRequest searchRequest) {
        //聚合

        //map 返回结果类型的key是规格参数的名称，eg:分类，品牌，CPU品牌，，value就是对应的聚合结果
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(buildQuery(searchRequest));

        final String BRAND_AGG_NAME = "brandAgg";

        final String CATEGORY_AGG_NAME = "categoryAgg";

        //添加分类聚合
        sourceBuilder.aggregation(AggregationBuilders.terms(CATEGORY_AGG_NAME).field("categoryId").size(30));

        //添加品牌聚合
        sourceBuilder.aggregation(AggregationBuilders.terms(BRAND_AGG_NAME).field("brandId").size(30));


        //其他参数聚合
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(100)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(100))));


        return this.goodRepository.aggregationBySourceBuilder(sourceBuilder)
                .map(aggregations -> {
                    Map<String, List<?>> filterResult = new LinkedHashMap<>();

                    //根据聚合名称获取聚合结果,分类聚合结果获取
                    Terms categoryAgg = aggregations.get(CATEGORY_AGG_NAME);

                    //从分类聚合结果中获取每个聚合分桶对应的key，转换成集合，这个key就是分类的每一个id
                    List<Long> categoryIdList = categoryAgg.getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket)
                                    .getKeyAsNumber()
                                    .longValue())
                            .collect(Collectors.toList());

                    if (!CollectionUtils.isEmpty(categoryIdList)) {
                        filterResult.put("分类", this.itemClient.listCategoryByIds(categoryIdList));
                    }

                    //从品牌聚合结果中获取每个聚合分桶对应的key，转换成集合，这个key就是品牌的每一个id
                    Terms brandAgg = aggregations.get(BRAND_AGG_NAME);

                    List<Long> brandIdList = brandAgg
                            .getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket)
                                    .getKeyAsNumber()
                                    .longValue())
                            .collect(Collectors.toList());

                    if (!CollectionUtils.isEmpty(brandIdList)) {
                        filterResult.put("品牌", this.itemClient.listBrandByIds(brandIdList));
                    }


                    //解析其他规格参数的聚合，由于规格参数加了nested修饰，所以首先要解析的类型为nested
                    Nested specAgg = aggregations.get("specAgg");

                    //从nested聚合内部以聚合名称nameAgg获取词条内容聚合
                    Terms nameAgg = specAgg.getAggregations().get("nameAgg");


                    nameAgg.getBuckets().forEach(nameBucket -> {

                        //specs.name字段聚合，聚合完成后，每一个bucket的key就是规格参数的名称
                        String bucketKey = nameBucket.getKeyAsString();

                        //根据value聚合名称获取当前规格参数名称下对应的属性值聚合聚合结果
                        Terms valueAgg = nameBucket.getAggregations().get("valueAgg");

                        //valueAgg聚合中每个key就是specs.name中存放的属性，对应的value集合
                        List<String> valueList = valueAgg
                                .getBuckets()
                                .stream()
                                .map(valueBucket -> ((Terms.Bucket) valueBucket).getKeyAsString())
                                .collect(Collectors.toList());

                        //把取出的属性名称，以及对应的值的集合压入结果中
                        filterResult.put(bucketKey, valueList);

                    });


                    return filterResult;
                });
    }
}
